﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Application = System.Windows.Forms.Application;
using Form = System.Windows.Forms.Form;
using Felbot.Interface;
using Felbot.Profiles;
using MBNCSUtil;

namespace Felbot.Protocol
{
    /// <summary>Controls connection to BotNet server.</summary>
    public class BotNetPacketThread : PacketThread
    {
        private Timer g_tmrSendNull;
        private LinkedList<string> g_llMessageQueue = new LinkedList<string>();
        private int g_iMessageQueueSeconds = 0;
        private Timer g_tmrMessageQueue;

        private bool g_bInChat = false;
        private string g_sNamespace = "BotNet";
        private int g_iNullResponse = 0;
        private string g_sLastWhisperSent = null;
        private string g_sLastWhisperRecieved = null;

        private frmBotNetProfile Profile;

        /// <summary>
        /// Describes the timer callback.
        /// </summary>
        private delegate void TimerCallbackDelegate(object stateInfo);
        /// <summary>
        /// Describes the timer callback.
        /// </summary>
        private delegate void AnonymousDelegate();

        /// <summary>
        /// This will construct a BotNetPacketThread
        /// <param name="main">The BotNet profile form that messages will be printed on.</param>
        /// <param name="mainx">Because this class is extended packetthread which requires this parameter.</param>
        /// </summary>
        public BotNetPacketThread(frmProfile mainx, frmBotNetProfile main) : base(mainx)
        {
            Profile = main;

            // Create the timer to send SID_NULLs
            g_tmrSendNull = new Timer(new TimerCallback((TimerCallbackDelegate) delegate(object stateInfo)
            {
                if (g_sckSocket != null && g_sckSocket.Connected && g_bConnected)
                {
                    if (g_iNullResponse < -6) //6 not responded to. sent every 300 seconds = 2000 seconds (30 minutes)
                    {
                        Profile.RichTextBox.AddTextNodes(new TextNode(Color.Red, "[BOTNET] Felbot detected server timeout!"));
                        Socket_Disconnect();
                        return;
                    }

                    Send_PACKET_IDLE();
                }
            }), null, 0, 300000);

            // Create the state moniting timer.
            g_tmrSckStateMonitor = new Timer(new TimerCallback((TimerCallbackDelegate) delegate(Object stateInfo)
            {
                #region Socket State Checking Delegate
                if (g_sckSocket != null && g_sckSocket.Connected != g_bSckConnected)
                {
                    g_bSckConnected = g_sckSocket.Connected;
                    if (g_sckSocket.Connected == true)
                    {
                        if (ProfileControl.Global.GetSetting("BotNet", "ProxyUse").ToBoolean(false))
                        {
                            Profile.RichTextBox.AddTextNodes(new TextNode(Color.GreenYellow, "[PROXY] Connected!"));
                        }
                        else
                        {
                            Profile.RichTextBox.AddTextNodes(new TextNode(Color.GreenYellow, "[BOTNET] Connected!"));
                        }
                        Socket_Connected();
                    }
                    else
                    {
                        if (ProfileControl.Global.GetSetting("BotNet", "ProxyUse").ToBoolean(false))
                        {
                            Profile.RichTextBox.AddTextNodes(new TextNode(Color.Red, "[PROXY] Disconnected!"));
                        }
                        else
                        {
                            Profile.RichTextBox.AddTextNodes(new TextNode(Color.Red, "[BOTNET] Disconnected!"));
                        }
                        Socket_Disconnect();
                    }
                }
                #endregion
            }), null, 0, 100);

            // Create the socket reading thread.
            g_thdSckRead = new Thread(new ThreadStart((AnonymousDelegate) delegate()
            /// <summary>
            /// This function will be constantly looping for the duration of the existance
            /// of an instance of this class, forked to the background in another Thread.
            /// This will handle all the reading of the socket.
            /// </summary>
            {
                byte[] m_bHeader = new byte[4];
                short m_sLength;
                byte m_bPacketID;
                byte[] m_bPacketData;
                byte[] m_bCHATMessage = new byte[512];
                while (true)
                {
                    Thread.Sleep(10);
                    if (g_sckSocket != null && g_sckSocket.Connected == true)
                    {
                        if (g_sckSocket.Available > 0)
                        {
                            try
                            {
                                #region Proxy Packet
                                base.Proxy();
                                #endregion

                                #region BotNet Packet
                                if (g_sckSocket.Available >= 3)
                                {
                                    g_sckSocket.Receive(m_bHeader, 0, 4, SocketFlags.Peek);

                                    m_bPacketID = m_bHeader[1];
                                    m_sLength = (short) BitConverter.ToUInt16(m_bHeader, 0);
                                    m_bPacketData = new byte[m_sLength];
                                    if (g_sckSocket.Available >= m_sLength)
                                    {
                                        g_sckSocket.Receive(m_bPacketData, 0, m_sLength, SocketFlags.None);
                                        if (ProfileForm.Main.ShowPackets)
                                        {
                                            ProfileForm.RichTextBox.AddTextNodes(
                                                new TextNode(
                                                    new Font(new FontFamily("Lucida Console"), 12, FontStyle.Regular, GraphicsUnit.Point),
                                                    Color.DodgerBlue,
                                                    "[BOTNET] RECIEVED PACKET 0x" + m_bPacketID.ToString("X") + " " + ((BotNetPacketIDs) m_bPacketID).ToString() + ":" + Environment.NewLine +
                                                    DataFormatter.Format(m_bPacketData)
                                                )
                                            );
                                        }
                                        ParsePacket(new DataReader(m_bPacketData));
                                    }
                                }
                                #endregion
                            }
                            catch (SocketException)
                            {
                            }
                        }
                    }
                }
            }));
            g_thdSckRead.IsBackground = true;
            g_thdSckRead.Start();
        }

        /// <summary>
        /// Parses the BotNet packet in the DataReader.
        /// </summary>
        /// <param name="a_prReader">The packet DataReader.</param>
        protected override void ParsePacket(DataReader a_prReader)
        {
            a_prReader.ReadByte();
            int m_iID = a_prReader.ReadByte();
            int m_iLength = a_prReader.ReadInt16();
            switch ((BotNetPacketIDs) m_iID)
            {
                #region 0x00 Handle PACKET_IDLE
                case BotNetPacketIDs.PACKET_IDLE:
                    Send_PACKET_IDLE();
                    break;
                #endregion

                #region 0x01 Handle PACKET_LOGON
                case BotNetPacketIDs.PACKET_LOGON:
                    int m_iHubLogonResult = a_prReader.ReadInt32();
                    #region Result Codes
                    switch (m_iHubLogonResult)
                    {
                        case 0x01:
                            Profile.RichTextBox.AddTextNodes(new TextNode(Color.Red, "[BOTNET] Hub logon failed."));
                            Socket_Disconnect();
                            return;
                        case 0x0C:
                            Profile.RichTextBox.AddTextNodes(new TextNode(Color.GreenYellow, "[BOTNET] Hub logon succeeded."));
                            break;
                        default:
                            Profile.RichTextBox.AddTextNodes(new TextNode(Color.Red, "[BOTNET] Hub logon failed. Unknown result code: 0x" + m_iHubLogonResult.ToString("X")));
                            Socket_Disconnect();
                            return;
                    }
                    #endregion
                    Profile.RichTextBox.AddTextNodes(new TextNode(Color.Yellow, "[BOTNET] Joining database and sending status.."));
                    Send_PACKET_STATSUPDATE();
                    break;
                #endregion

                #region Default
                default:
                    Profile.RichTextBox.AddTextNodes(new TextNode(Color.Red, "[BOTNET] Unhandled BotNet packet received! 0x" + m_iID.ToString("X")));
                    break;
                #endregion
            }
        }

        private void Send_PACKET_IDLE()
        {
            BotNetPacket packet = new BotNetPacket(BotNetPacketIDs.PACKET_IDLE);
            SendPacket(packet.GetData());
        }

        private void Send_PACKET_LOGON()
        {
            BotNetPacket packet = new BotNetPacket(BotNetPacketIDs.PACKET_LOGON);
            packet.InsertCString(ProfileControl.Global.GetSetting("BotNet", "HubUsername").ToString(""));
            packet.InsertCString(ProfileControl.Global.GetSetting("BotNet", "HubPassword").ToString(""));
            SendPacket(packet.GetData());
        }

        private void Send_PACKET_STATSUPDATE()
        {
            Profile profile = ProfileControl.ProfileList[ProfileControl.Global.GetSetting("BotNet", "OnlineMirror").ToInteger(0)];
            frmProfile pform = null;
            string username;
            string channel;
            string servset;
            IPAddress server;
            if (profile.Open)
            {
                foreach (Form form in Application.OpenForms)
                {
                    if (form.Text.StartsWith("Profile " + profile.ProfileName + " "))
                    {
                        pform = (frmProfile) form;
                        break;
                    }
                }
                if (pform.ChannelList.ChannelName != "")
                {
                channel = pform.ChannelList.ChannelName;
                }
                else
                {   
                channel = "<Offline>";
                }
            }
            else
            {
                channel = "<Offline>";
            }
            username = profile.GetSetting("Connection", "Username").ToString("");
            servset = profile.GetSetting("Connection", "Server").ToString("");
            try
            {
                server = IPAddress.Parse(new TcpClient(servset, 6112).Client.RemoteEndPoint.ToString().Split(':')[0]);
            }
            catch (SocketException)
            {
                Profile.RichTextBox.AddTextNodes(new TextNode(Color.Red, "[BOTNET] Unable to resolve \"" + servset + "\" to an IP address."));
                return;
            }
            BotNetPacket packet = new BotNetPacket(BotNetPacketIDs.PACKET_STATSUPDATE);
            packet.InsertCString(username);
            packet.InsertCString(channel);
            packet.InsertByteArray(server.GetAddressBytes());
            if (ProfileControl.Global.GetSetting("BotNet", "DBChoice").ToInteger(0) == 0)
            {
                packet.InsertByte(0x00); //empty db string
            }
            else
            {
                packet.InsertCString(ProfileControl.Global.GetSetting("BotNet", "DBName").ToString("") + " " + ProfileControl.Global.GetSetting("BotNet", "DBPass").ToString(""));
            }
            packet.InsertByte(0x00); //cycling off
            SendPacket(packet.GetData());
        }

        /// <summary>
        /// This is a public method to begin connecting the socket.
        /// </summary>
        public override bool Socket_Connect()
        {
            if (!base.Socket_Connect())
                return false;

            #region Connection Sanity
            if (ProfileControl.Global.GetSetting("BotNet", "Server").ToString("www.valhallalegends.com") == "")
                throw new ArgumentOutOfRangeException(null, "The server setting is empty.");
            if (ProfileControl.Global.GetSetting("BotNet", "HubUsername").ToString("") == "")
                throw new ArgumentOutOfRangeException(null, "The hub username setting is empty.");
            if (ProfileControl.Global.GetSetting("BotNet", "HubPassword").ToString("") == "")
                throw new ArgumentOutOfRangeException(null, "The hub password setting is empty.");
            #endregion

            g_sckSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            new Thread(new ThreadStart(delegate()
            {
                try
                {
                    if (ProfileControl.Global.GetSetting("BotNet", "ProxyUse").ToBoolean(false))
                    {
                        Profile.RichTextBox.AddTextNodes(new TextNode(Color.Yellow, "[PROXY] Connecting to " +
                            ProfileControl.Global.GetSetting("BotNet", "ProxyServer").ToString("") + ":" + ProfileControl.Global.GetSetting("BotNet", "ProxyPort").ToString("1080") + ".."));
                        g_sckSocket.Connect(ProfileControl.Global.GetSetting("BotNet", "ProxyServer").ToString(""), ProfileControl.Global.GetSetting("BotNet", "ProxyPort").ToInteger(1080));
                    }
                    else
                    {
                        Profile.RichTextBox.AddTextNodes(new TextNode(Color.Yellow, "[BOTNET] Connecting to " +
                            ProfileControl.Global.GetSetting("BotNet", "Server").ToString("") + ".."));
                        g_sckSocket.Connect(ProfileControl.Global.GetSetting("BotNet", "Server").ToString(""), 0x5554);
                    }
                }
                catch (Exception e)
                {
                    if (e.Message == "An existing connection was forcibly closed by the remote host")
                    {
                        if (g_iReconnectAttempts > 1)
                        {
                            g_iReconnectAttempts = 0;
                            Profile.RichTextBox.AddTextNodes(new TextNode(Color.Red, "[BOTNET] You are IPBanned, or you have lost your internet connection.  Please try connecting later."));
                            Socket_Disconnect();
                            return;
                        }
                        else
                        {
                            Profile.RichTextBox.AddTextNodes(new TextNode(Color.Red, "[BOTNET] You have been IPBanned or your connection has been interrupted, reconnecting.. (" + (++g_iReconnectAttempts).ToString() + ")"));
                            Socket_Connect();
                            return;
                        }
                    }
                    Profile.RichTextBox.AddTextNodes(new TextNode(Color.Red, "[BOTNET] Caught exception in Socket_Connect(): " + Environment.NewLine + e.ToString()));
                    return;
                }
            })).Start();
            return true;
        }

        /// <summary>
        /// This is a public method to disconnect the socket.
        /// </summary>
        public override bool Socket_Disconnect()
        {
            if (base.Socket_Disconnect())
            {
                g_iNullResponse = 0;
                g_sNamespace = "Offline";
                g_sLastWhisperSent = null;
                g_sLastWhisperRecieved = null;
                Profile.BotNetList.Disconnect();
                return true;
            }
            return false;
        }

        internal override void Socket_ConnectedComplete()
        {
            Profile.RichTextBox.AddTextNodes(new TextNode(Color.Yellow, "[BOTNET] Logging on with HubID Felbot.."));
            Send_PACKET_LOGON();
        }

        internal override void SendPacket(byte[] bPacket)
        {
            if (!g_bConnected)
                return;
            try
            {
                g_sckSocket.Send(bPacket);
                if (Profile.Main.ShowPackets)
                {
                    Profile.RichTextBox.AddTextNodes(
                        new TextNode(
                            new Font(new FontFamily("Lucida Console"), 12, FontStyle.Regular, GraphicsUnit.Point),
                            Color.DodgerBlue,
                            "[BOTNET] SENT PACKET 0x" + bPacket[2].ToString("X") + ((BotNetPacketIDs) bPacket[2]).ToString() + ":" + Environment.NewLine +
                            DataFormatter.Format(bPacket)
                        )
                    );
                }
            }
            catch (Exception e)
            {
                if (e.Message == "An existing connection was forcibly closed by the remote host")
                {
                    g_iReconnectAttempts = 0;
                    Profile.RichTextBox.AddTextNodes(new TextNode(Color.Red, "[BOTNET] You are IPBanned, or you have lost your internet connection.  Please try connecting later."));
                    Socket_Disconnect();
                    return;
                }
                Profile.RichTextBox.AddTextNodes(new TextNode(Color.Red, "[BOTNET] Caught exception in SendPacket(): " + Environment.NewLine + e.ToString()));
            }
        }

        internal override void SendByte(byte bByte)
        {
            //this won't be called, do nothing
        }
    }

    //McpPacket, same as BncsPacket except different header.
    /*
    MBNCSUtil -- Managed Battle.net Authentication Library
    Copyright (C) 2005-2008 by Robert Paveza

    Redistribution and use in source and binary forms, with or without modification, 
    are permitted provided that the following conditions are met: 

    1.) Redistributions of source code must retain the above copyright notice, 
    this list of conditions and the following disclaimer. 
    2.) Redistributions in binary form must reproduce the above copyright notice, 
    this list of conditions and the following disclaimer in the documentation 
    and/or other materials provided with the distribution. 
    3.) The name of the author may not be used to endorse or promote products derived 
    from this software without specific prior written permission. 
    	
    See LICENSE.TXT that should have accompanied this software for full terms and 
    conditions.

    */
    /// <summary>
    /// Completes a <see cref="DataBuffer">DataBuffer</see> implementation with the additional
    /// data used by the MCP protocol.
    /// </summary>
    internal sealed class BotNetPacket : DataBuffer
    {
        private BotNetPacketIDs m_id;

        /// <summary>
        /// Creates a new BotNet packet with the specified packet ID.
        /// </summary>
        /// <param name="id">The MCP packet ID.</param>
        public BotNetPacket(BotNetPacketIDs id)
            : base()
        {
            m_id = id;
        }

        /// <summary>
        /// Gets or sets the ID of the packet as it was specified when it was created.
        /// </summary>
        public byte PacketID
        {
            get { return (byte) m_id; }
            set { m_id = (BotNetPacketIDs) value; }
        }

        /// <summary>
        /// Gets the total number of bytes in this packet, including the three-byte header.
        /// </summary>
        public override int Count
        {
            get
            {
                return base.Count + 4;
            }
        }

        /// <summary>
        /// Gets the data in this packet, including the three-byte header.
        /// </summary>
        /// <returns>A byte array containing the packet data.</returns>
        public override byte[] GetData()
        {
            byte[] data = new byte[Count];
            byte[] len = BitConverter.GetBytes((ushort) (Count & 0xffff));
            data[0] = 0x01;
            data[1] = (byte) m_id;
            data[2] = len[0];
            data[3] = len[1];
            byte[] baseData = base.GetData();

            Buffer.BlockCopy(baseData, 0, data, 4, baseData.Length);

            return data;
        }

        /// <summary>
        /// Gets a hex representation of this buffer.
        /// </summary>
        /// <returns>A string representing this buffer's contents in hexadecimal notation.</returns>
        public override string ToString()
        {
            return DataFormatter.Format(GetData());
        }
    }
}
